package orm

import (
	"bytes"
	"context"
	"database/sql"
	"fmt"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
	"pay/pkg/env"
	"pay/pkg/log"
	"reflect"
	"strings"
	"time"
	"unicode"
)

// Config mysql config.
type Config struct {
	DSN            string        `mapstructure:"dsn"`             // data source name.
	Active         int           `mapstructure:"active"`          // pool
	Idle           int           `mapstructure:"idle"`            // pool
	IdleTimeout    time.Duration `mapstructure:"idle_timeout"`    // connect max life time.
	SkipNormalLog  bool          `mapstructure:"skip_normal_log"` // non error log would be skipped
	NamingStrategy schema.NamingStrategy
}

type Driver interface {
	Open(dsn string) gorm.Dialector
}

func NewConnection(c *Config, env env.Type, dialector gorm.Dialector) (db *gorm.DB, err error) {
	if db, err = gorm.Open(dialector, &gorm.Config{
		NamingStrategy: c.NamingStrategy,
		Logger:         &Logger{LogLevel: logger.Info, env: env, skipNormalLog: c.SkipNormalLog},
	}); err != nil {
		err = fmt.Errorf("%s启动失败, err=%s", dialector.Name(), err.Error())
		return
	}

	var sqlDB *sql.DB
	if sqlDB, err = db.DB(); err != nil {
		err = fmt.Errorf("%s创建连接池失败, err=%s", dialector.Name(), err.Error())
		return
	}
	sqlDB.SetMaxIdleConns(c.Idle)
	sqlDB.SetMaxOpenConns(c.Active)
	sqlDB.SetConnMaxLifetime(c.IdleTimeout)
	return
}

func CloseConnection(ctx context.Context, db *gorm.DB) {
	if db != nil {
		if sqlDB, err := db.DB(); err == nil {
			_ = sqlDB.Close()
			log.FromContext(ctx).Info("数据库连接已关闭")
		}
	}
}

func FormatColumnMap(obj interface{}) (query string, args []interface{}, data map[string]interface{}) {
	t := reflect.TypeOf(obj).Elem()
	v := reflect.ValueOf(obj).Elem()
	queryKeys := make([]string, 0)
	data = make(map[string]interface{})
	for i := 0; i < t.NumField(); i++ {
		fieldName := t.Field(i).Name
		tag := t.Field(i).Tag.Get("gorm")
		buffer := bytes.NewBuffer([]byte{})
		for j, k := range fieldName {
			if unicode.IsUpper(k) {
				if j != 0 {
					buffer.WriteString("_")
				}
				buffer.WriteRune(unicode.ToLower(k))
			} else {
				buffer.WriteRune(k)
			}
		}
		columnName := buffer.String()
		if strings.Contains(tag, "primary") {
			queryKeys = append(queryKeys, fmt.Sprintf("%s = ?", columnName))
			args = append(args, v.Field(i).Interface())
			continue
		}
		switch columnName {
		case "created_at", "updated_at":
			continue
		}
		data[columnName] = v.Field(i).Interface()
	}
	query = strings.Join(queryKeys, " AND ")
	return
}
